package com.lemon.rule.express.util;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.beanutils.PropertyUtils;

import com.lemon.rule.express.OperatorOfNumber;

@SuppressWarnings("unchecked")
public class ExpressUtil {
    public static final String DT_STRING = "String";
    public static final String DT_SHORT = "Short";
    public static final String DT_INTEGER = "Integer";
    public static final String DT_LONG = "Long";
    public static final String DT_DOUBLE = "Double";
    public static final String DT_FLOAT = "Float";
    public static final String DT_BYTE = "Byte";
    public static final String DT_CHAR = "Char";
    public static final String DT_BOOLEAN = "Boolean";
    public static final String DT_DATE = "Date";
    public static final String DT_TIME = "Time";
    public static final String DT_DATETIME = "DateTime";
    public static final String DT_OBJECT = "Object";

    public static final String DT_short = "short";
    public static final String DT_int = "int";
    public static final String DT_long = "long";
    public static final String DT_double = "double";
    public static final String DT_float = "float";
    public static final String DT_byte = "byte";
    public static final String DT_char = "char";
    public static final String DT_boolean = "boolean";

    public static Map<String, Object> methodCache = new ConcurrentHashMap<String, Object>();

    public static Class<?>[][] classMatchs = new Class[][] { { BigDecimal.class, double.class }, { BigDecimal.class, float.class }, { BigDecimal.class, long.class }, { BigDecimal.class, int.class }, { BigDecimal.class, short.class }, { BigDecimal.class, byte.class }, { double.class, float.class }, { double.class, long.class }, { double.class, int.class }, { double.class, short.class }, { double.class, byte.class }, { double.class, BigDecimal.class }, { float.class, long.class }, { float.class, int.class }, { float.class, short.class }, { float.class, byte.class }, { float.class, BigDecimal.class }, { long.class, int.class }, { long.class, short.class }, { long.class, byte.class }, { int.class, short.class }, { int.class, byte.class }, { short.class, byte.class }, { char.class, Character.class }, { Character.class, char.class }, { boolean.class, Boolean.class }, { Boolean.class, boolean.class } };

    public static Class<?> getSimpleDataType(Class<?> aClass) {
        if (Integer.class.equals(aClass))
            return Integer.TYPE;
        if (Short.class.equals(aClass))
            return Short.TYPE;
        if (Long.class.equals(aClass))
            return Long.TYPE;
        if (Double.class.equals(aClass))
            return Double.TYPE;
        if (Float.class.equals(aClass))
            return Float.TYPE;
        if (Byte.class.equals(aClass))
            return Byte.TYPE;
        if (Character.class.equals(aClass))
            return Character.TYPE;
        if (Boolean.class.equals(aClass))
            return Boolean.TYPE;
        return aClass;
    }

    public static boolean isAssignable(Class<?> target, Class<?> source) {
        if (target == source)
            return true;
        if (target.isArray() && source.isArray()) {
            return isAssignable(target.getComponentType(), source.getComponentType());
        }
        return isAssignablePrivate(target, source);
    }

    public static boolean isAssignablePrivate(Class<?> target, Class<?> source) {
        if (target == source)
            return true;

        if (target == null)
            return false;
        if (source == null)//nullת��
            return !target.isPrimitive();

        if (target.isAssignableFrom(source) == true) {
            return true;
        }
        if (source.isPrimitive() && target == Object.class) {
            return true;
        }

        if (target.isPrimitive() == false) {
            if (target == Byte.class)
                target = byte.class;
            else if (target == Short.class)
                target = short.class;
            else if (target == Integer.class)
                target = int.class;
            else if (target == Long.class)
                target = long.class;
            else if (target == Float.class)
                target = float.class;
            else if (target == Double.class)
                target = double.class;
        }
        if (source.isPrimitive() == false) {
            if (source == Byte.class)
                source = byte.class;
            else if (source == Short.class)
                source = short.class;
            else if (source == Integer.class)
                source = int.class;
            else if (source == Long.class)
                source = long.class;
            else if (source == Float.class)
                source = float.class;
            else if (source == Double.class)
                source = double.class;
        }
        if (target == source)// ת������Ҫ���ж�һ��
            return true;

        for (int i = 0; i < classMatchs.length; i++) {
            if (target == classMatchs[i][0] && source == classMatchs[i][1]) {
                return true;
            }
        }

        return false;

    }

    public static boolean isAssignableOld(Class<?> lhsType, Class<?> rhsType) {
        if (lhsType == null)
            return false;
        if (rhsType == null)
            return !lhsType.isPrimitive();

        if (lhsType.isPrimitive() && rhsType.isPrimitive()) {
            if (lhsType == rhsType)
                return true;

            if ((rhsType == Byte.TYPE) && (lhsType == Short.TYPE || lhsType == Integer.TYPE || lhsType == Long.TYPE || lhsType == Float.TYPE || lhsType == Double.TYPE))
                return true;

            if ((rhsType == Short.TYPE) && (lhsType == Integer.TYPE || lhsType == Long.TYPE || lhsType == Float.TYPE || lhsType == Double.TYPE))
                return true;

            if ((rhsType == Character.TYPE) && (lhsType == Integer.TYPE || lhsType == Long.TYPE || lhsType == Float.TYPE || lhsType == Double.TYPE))
                return true;

            if ((rhsType == Integer.TYPE) && (lhsType == Long.TYPE || lhsType == Float.TYPE || lhsType == Double.TYPE))
                return true;

            if ((rhsType == Long.TYPE) && (lhsType == Float.TYPE || lhsType == Double.TYPE))
                return true;

            if ((rhsType == Float.TYPE) && (lhsType == Double.TYPE))
                return true;
        } else if (lhsType.isAssignableFrom(rhsType))
            return true;

        return false;
    }

    public static boolean isSignatureAssignable(Class<?>[] from, Class<?>[] to) {
        for (int i = 0; i < from.length; i++)
            if (!isAssignable(to[i], from[i]))
                return false;
        return true;
    }

    public static int findMostSpecificSignature(Class<?>[] idealMatch, Class<?>[][] candidates) {
        Class<?>[] bestMatch = null;
        int bestMatchIndex = -1;

        for (int i = candidates.length - 1; i >= 0; i--) {// �ȴӻ��࿪ʼ����
            Class<?>[] targetMatch = candidates[i];
            if (ExpressUtil.isSignatureAssignable(idealMatch, targetMatch) && ((bestMatch == null) || ExpressUtil.isSignatureAssignable(targetMatch, bestMatch))) {
                bestMatch = targetMatch;
                bestMatchIndex = i;
            }
        }

        if (bestMatch != null)
            return bestMatchIndex;
        else
            return -1;
    }

    public static String createCacheKey(Class<?> aBaseClass, String aMethodName, Class<?>[] aTypes, boolean aPublicOnly, boolean aIsStatic) {
        StringBuilder builder = new StringBuilder();
        builder.append(aPublicOnly).append("-").append(aIsStatic).append("-");
        builder.append(aBaseClass.getName()).append(".").append(aMethodName).append("(");
        if (aTypes == null) {
            builder.append("null");
        } else {
            for (int i = 0; i < aTypes.length; i++) {
                if (i > 0) {
                    builder.append(",");
                }
                if (aTypes[i] == null) {
                    builder.append("null");
                } else {
                    builder.append(aTypes[i].getName());
                }
            }
        }
        builder.append(")");
        return builder.toString();

    }

    public static Method findMethodWithCache(Class<?> baseClass, String methodName, Class<?>[] types, boolean publicOnly, boolean isStatic) {
        String key = createCacheKey(baseClass, methodName, types, publicOnly, isStatic);
        Object result = methodCache.get(key);
        if (result == null) {
            result = findMethod(baseClass, methodName, types, publicOnly, isStatic);
            if (result == null) {
                methodCache.put(key, void.class);
            } else {
                ((Method) result).setAccessible(true);
                methodCache.put(key, result);
            }
        } else if (result == void.class) {
            result = null;
        }
        return (Method) result;
    }

    public static Method findMethod(Class<?> baseClass, String methodName, Class<?>[] types, boolean publicOnly, boolean isStatic) {
        List<Method> candidates = gatherMethodsRecursive(baseClass, methodName, types.length, publicOnly, isStatic, null /* candidates */);
        Method method = findMostSpecificMethod(types, (Method[]) candidates.toArray(new Method[0]));
        return method;
    }

    public static Constructor<?> findConstructor(Class<?> baseClass, Class<?>[] types) {
        Constructor<?>[] constructors = baseClass.getConstructors();
        List<Constructor<?>> constructorList = new ArrayList<Constructor<?>>();
        List<Class<?>[]> listClass = new ArrayList<Class<?>[]>();
        for (int i = 0; i < constructors.length; i++) {
            if (constructors[i].getParameterTypes().length == types.length) {
                listClass.add(constructors[i].getParameterTypes());
                constructorList.add(constructors[i]);
            }
        }

        int match = findMostSpecificSignature(types, (Class[][]) listClass.toArray(new Class[0][]));
        return match == -1 ? null : constructorList.get(match);
    }

    public static Method findMostSpecificMethod(Class<?>[] idealMatch, Method[] methods) {
        Class<?>[][] candidateSigs = new Class[methods.length][];
        for (int i = 0; i < methods.length; i++)
            candidateSigs[i] = methods[i].getParameterTypes();

        int match = findMostSpecificSignature(idealMatch, candidateSigs);
        return match == -1 ? null : methods[match];

    }

    private static List<Method> gatherMethodsRecursive(Class<?> baseClass, String methodName, int numArgs, boolean publicOnly, boolean isStatic, List<Method> candidates) {
        if (candidates == null)
            candidates = new ArrayList<Method>();

        addCandidates(baseClass.getDeclaredMethods(), methodName, numArgs, publicOnly, isStatic, candidates);

        Class<?>[] intfs = baseClass.getInterfaces();
        for (int i = 0; i < intfs.length; i++)
            gatherMethodsRecursive(intfs[i], methodName, numArgs, publicOnly, isStatic, candidates);

        Class<?> superclass = baseClass.getSuperclass();
        if (superclass != null)
            gatherMethodsRecursive(superclass, methodName, numArgs, publicOnly, isStatic, candidates);

        return candidates;
    }

    private static List<Method> addCandidates(Method[] methods, String methodName, int numArgs, boolean publicOnly, boolean isStatic, List<Method> candidates) {
        for (int i = 0; i < methods.length; i++) {
            Method m = methods[i];
            if (m.getName().equals(methodName) && (m.getParameterTypes().length == numArgs) && (publicOnly == false || isPublic(m) && (isStatic == false || isStatic(m))))
                candidates.add(m);
        }
        return candidates;
    }

    public static boolean isPublic(Class<?> c) {
        return Modifier.isPublic(c.getModifiers());
    }

    public static boolean isPublic(Method m) {
        return Modifier.isPublic(m.getModifiers());
    }

    public static boolean isStatic(Method m) {
        return Modifier.isStatic(m.getModifiers());
    }

    public static Class<?> getJavaClass(String type) {
        int index = type.indexOf("[]");
        if (index < 0)
            return getJavaClassInner(type);

        StringBuilder arrayString = new StringBuilder();
        arrayString.append("[");
        String baseType = type.substring(0, index);
        while ((index = type.indexOf("[]", index + 2)) >= 0) {
            arrayString.append("[");
        }
        Class<?> baseClass = getJavaClassInner(baseType);

        try {
            String baseName = "";
            if (baseClass.isPrimitive() == false) {
                return loadClass(arrayString.toString() + "L" + baseClass.getName() + ";");
            } else {
                if (baseClass.equals(boolean.class)) {
                    baseName = "Z";
                } else if (baseClass.equals(byte.class)) {
                    baseName = "B";
                } else if (baseClass.equals(char.class)) {
                    baseName = "C";
                } else if (baseClass.equals(double.class)) {
                    baseName = "D";
                } else if (baseClass.equals(float.class)) {
                    baseName = "F";
                } else if (baseClass.equals(int.class)) {
                    baseName = "I";
                } else if (baseClass.equals(long.class)) {
                    baseName = "J";
                } else if (baseClass.equals(short.class)) {
                    baseName = "S";
                }
                return loadClass(arrayString.toString() + baseName);
            }
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(ex);
        }

    }

    public static Class<?> getJavaClassInner(String type) {

        if (type.equals(DT_STRING))
            return String.class;
        if (type.equals(DT_SHORT))
            return Short.class;
        if (type.equals(DT_INTEGER))
            return Integer.class;
        if (type.equals(DT_LONG))
            return Long.class;
        if (type.equals(DT_DOUBLE))
            return Double.class;
        if (type.equals(DT_FLOAT))
            return Float.class;
        if (type.equals(DT_BYTE))
            return Byte.class;
        if (type.equals(DT_CHAR) || type.equals("Character"))
            return Character.class;
        if (type.equals(DT_BOOLEAN))
            return Boolean.class;
        if (type.equals(DT_DATE))
            return java.sql.Date.class;
        if (type.equals(DT_TIME))
            return java.sql.Time.class;
        if (type.equals(DT_DATETIME))
            return java.sql.Timestamp.class;
        if (type.equals(DT_OBJECT))
            return Object.class;
        if (type.equals(DT_short))
            return short.class;
        if (type.equals(DT_int))
            return int.class;
        if (type.equals(DT_long))
            return long.class;
        if (type.equals(DT_double))
            return double.class;
        if (type.equals(DT_float))
            return float.class;
        if (type.equals(DT_byte))
            return byte.class;
        if (type.equals(DT_char))
            return char.class;
        if (type.equals(DT_boolean))
            return boolean.class;
        try {
            return loadClass(type);
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(ex);
        }
    }

    public static String getClassName(Class<?> className) {
        if (className == null) {
            return null;
        }
        String name = className.getName();
        return getClassName(name);
    }

    private static String getClassName(String name) {
        String arrays = "";
        if (name.indexOf("[") >= 0) {
            int point = 0;
            while (name.charAt(point) == '[') {
                arrays = arrays + "[]";
                ++point;
            }
            if (name.charAt(point) == 'L') {
                name = name.substring(point + 1, name.length() - 1);
            } else if (name.charAt(point) == 'Z') {
                name = "boolean";
            } else if (name.charAt(point) == 'B') {
                name = "byte";
            } else if (name.charAt(point) == 'C') {
                name = "char";
            } else if (name.charAt(point) == 'D') {
                name = "double";
            } else if (name.charAt(point) == 'F') {
                name = "float";
            } else if (name.charAt(point) == 'I') {
                name = "int";
            } else if (name.charAt(point) == 'J') {
                name = "long";
            } else if (name.charAt(point) == 'S') {
                name = "short";
            }
        }
        int index = name.lastIndexOf('.');
        if (index > 0 && name.substring(0, index).equals("java.lang") == true) {
            name = name.substring(index + 1);
        }
        name = name + arrays;
        return name;
    }

    public static Class<?> loadClass(String name) throws ClassNotFoundException {
        return Class.forName(name);
    }

    /**
     * �滻�ַ����еĲ��� replaceString("$1ǿ��$2ʵʩ$2",new String[]{"qq","ff"})
     * ="qq ǿ�� ff ʵʩ ff"
     * 
     * @param str
     * @param parameters
     * @return
     * @throws Exception
     */
    public static String replaceString(String str, Object[] parameters) throws Exception {
        if (str == null || parameters == null || parameters.length == 0) {
            return str;
        }
        Pattern p = Pattern.compile("\\$\\d+");
        Matcher m = p.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            int index = Integer.parseInt(m.group().substring(1)) - 1;
            if (index < 0 || index >= parameters.length) {
                throw new Exception("���õĲ���λ��$" + (index + 1) + "�����˷�Χ " + parameters.length);
            }
            m.appendReplacement(sb, " " + parameters[index].toString() + " ");
        }
        m.appendTail(sb);
        return sb.toString();
    }

    public static Object getProperty(Object bean, Object name) {
        try {
            if (bean.getClass().isArray() && name.equals("length")) {
                return Array.getLength(bean);
            } else if (bean instanceof Class) {
                if (name.equals("class")) {
                    return bean;
                } else {
                    Field f = ((Class<?>) bean).getDeclaredField(name.toString());
                    return f.get(null);
                }
            } else if (bean instanceof Map) {
                return ((Map<?, ?>) bean).get(name);
            } else {
                Object obj = PropertyUtils.getProperty(bean, name.toString());
                return obj;
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Class<?> getPropertyClass(Object bean, Object name) {
        try {
            if (bean.getClass().isArray() && name.equals("length")) {
                return int.class;
            } else if (bean instanceof Class) {
                if (name.equals("class")) {
                    return Class.class;
                } else {
                    Field f = ((Class<?>) bean).getDeclaredField(name.toString());
                    return f.getType();
                }
            } else if (bean instanceof Map) {
                Object o = ((Map<?, ?>) bean).get(name);
                if (o == null) {
                    return null;
                } else {
                    return o.getClass();
                }
            } else {
                return PropertyUtils.getPropertyDescriptor(bean, name.toString()).getPropertyType();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void setProperty(Object bean, Object name, Object value) {
        try {
            if (bean instanceof Class) {
                Field f = ((Class<?>) bean).getDeclaredField(name.toString());
                f.set(null, value);
            } else if (bean instanceof Map) {
                ((Map<Object, Object>) bean).put(name, value);
            } else {
                Class<?> filedClass = PropertyUtils.getPropertyType(bean, name.toString());
                PropertyUtils.setProperty(bean, name.toString(), ExpressUtil.castObject(value, filedClass, false));
            }
        } catch (Exception e) {
            throw new RuntimeException("���ܷ���" + bean + "��property:" + name, e);
        }
    }

    public static Object[] transferArray(Object[] values, Class<?>[] types) {
        Object[] result = new Object[values.length];
        for (int i = 0; i < result.length; i++) {
            result[i] = castObject(values[i], types[i], false);
        }
        return result;
    }

    /**
     * 
     * @param value
     * @param type
     * @param isForce �Ƿ�ǿ��ת��
     * @return
     * @throws ExceptionK
     */
    public static Object castObject(Object value, Class<?> type, boolean isForce) {
        if (value == null)
            return null;
        if (value.getClass() == type || type.isAssignableFrom(value.getClass())) {
            return value;
        }
        if (value instanceof Number && (type.isPrimitive() || Number.class.isAssignableFrom(type))) {
            return OperatorOfNumber.transfer((Number) value, type, isForce);
        } else {
            return value;
        }
    }

}
